/* Font.cpp
Copyright (c) 2008 Michael Zahniser
Please note the license terms (MIT license) at the end of this document.

Function definitions for the sdl::Font class.
*/

#include "Font.h"

#include "PixMap.h"

#include "SDL.h"

#include <cstring>

using std::memset;



// Load:
// Load an image file and use it as the glyphs for the font.
// The file should have ASCII characters 33 to 126 ('!' to '~') in order, with
// at least one empty column (completely transparent) after each character.
// After the ~, include a glyph to be used for undefined characters.
void Font::Load(const string &path)
{
	// Try to load the image.
	LoadImage(path);
	Initialize();
}



void Font::Load(const string &data, int width, int height)
{
	LoadData(data, width, height);
	Initialize();
}



void Font::Initialize()
{
	if(!*this)
		return;
	
	// Initialize the previous glyph index to 0 (space).
	previous = 0;
	
	// The width of a space is dependent on the character height.
	SPACE = Height() / SPACE_FRACTION;
	TAB = 4 * SPACE;
	
	// Clear the character data arrays.
	memset(offset, 0, sizeof(offset));
	scanline.clear();
	scanline.reserve(GLYPHS * Height());
		
	// For each character:
	// Advance column until a blank column is found.
	// Then, advance until a non-blank column.
	PixMap pix(*this);
	int pitch = pix.Pitch();
	int mask = pix.Format().Amask;

	// Get iterators to the offset array and the image.
	int *outIt = offset;
	int *outEnd = outIt + GLYPHS * 2;
	// Placeholder for a space.
	*outIt++ = 0;
	*outIt++ = 0;
	Uint32 *it = pix.Begin(0);
	bool wasEmpty = true;
	
	for(int column = 0; (column < pix.Width()) & (outIt != outEnd); ++column)
	{
		// Check whether this column is empty.
		bool isEmpty = true;
		for(int row = 0; row < Height(); ++row)
			isEmpty &= !(it[row * pitch] & mask);
		
		// Store each offset where the image goes from blank to not blank or vice versa.
		if(isEmpty != wasEmpty)
		{
			*outIt = column;
			++outIt;
			wasEmpty = isEmpty;
		}

		++it;
	}

	// Generate scanline data for each row of each glyph.
	for(outIt = offset; outIt != outEnd; ++outIt)
	{
		int startX = *outIt;
		++outIt;
		int endX = *outIt;

		for(int row = 0; row < Height(); ++row)
			scanline.push_back(CharScanline(*this, row, startX, endX));
	}
	
	// Cache the kerning for each glyph pair.
	for(int a = 0; a < GLYPHS; ++a)
		for(int b = 0; b < GLYPHS; ++b)
			kerning[a + b * GLYPHS] = Kerning(a, b);
}



// CharIndex:
// Convert a char to its index in the glyph image.
int Font::CharIndex(char a) const
{
	int index = a - ' ';

	// Space is -1. Characters with no glyph are index GLYPHS - 1.
	// All control characters map to space, for simplicity.
	if(index > GLYPHS - 1)
		index = GLYPHS - 1;

	return index;
}



// Kerning:
// Get the minimum distance between the characters with the given two indices.
// (Do _not_ give this function raw character values.)
int Font::Kerning(int aIndex, int bIndex) const
{
	// Kerning for a space:
	if(!aIndex | !bIndex)
		return 0;
	
	// Find the scanline where the given glyphs are closest to each other.
	// We will adjust the kerning so that the distance between them at that
	// point is at least KERNING.
	int minDistance = 100;

	// Get the start of the scanlines of each glyph.
	vector<CharScanline>::const_iterator aScanline = scanline.begin() + aIndex * Height();
	vector<CharScanline>::const_iterator bScanline = scanline.begin() + bIndex * Height();
	
	for(int row = 0; row < Height(); ++row)
	{
		// Get the distance between the scanlines.
		int distance = aScanline->DistanceTo(*bScanline);
		++aScanline;
		++bScanline;
		
		// If both scanlines have pixels in this row, update the area.
		if((distance != 0) & (distance < minDistance))
			minDistance = distance;
	}
	
	// Occasionally, there may be no rows where both glyphs have pixels.
	// For example, the two glyphs may be '"' and '_'.
	if(minDistance == 100)
		return 0;
	
	// The value we will return is the number of pixels that should be
	// added to the current draw position in order to make the distance
	// between these glyphs equal to KERNING.
	int kerning = KERNING - minDistance;
	
	// Never shift a glyph backward by more than 1 pixel, as this can
	// look rather strange (for example, kerning "->" without this check
	// ends up with the '-' nestled far inside the '>').
	if(kerning < -1)
		kerning = -1;
	
	return kerning;
}



// CharScanline constructor:
// Extract scanline information from the given row of the given surface.
Font::CharScanline::CharScanline(const Surface &surface, int row, int minX, int maxX)
	: width(maxX - minX),
	firstPixel(255), lastPixel(255),
	firstAlpha(0), lastAlpha(0)
{
	ConstPixMap pix(surface);

	// alpha = (pixel & mask) >> shift.
	Uint32 mask = pix.Format().Amask;
	Uint32 shift = pix.Format().Ashift;

	// Start iterating at the beginning of this scanline.
	const Uint32 *it = pix.Begin(row) + minX;
	
	// Flag to determine if we've encountered the first occupied pixel.
	bool first = true;
	for(int x = 0; x < width; ++x, ++it)
	{
		unsigned char alpha = static_cast<unsigned char>((*it & mask) >> shift);
		if(alpha)
		{
			if(first)
			{
				// If this is the first occupied pixel,
				// store its column and alpha value in firstPixel.
				firstPixel = x;
				firstAlpha = alpha;
				first = false;
			}
			
			// Store the position and alpha of the last pixel in the row.
			lastPixel = x;
			lastAlpha = alpha;
		}
	}
}



// CharScanline DistanceTo:
// Measure the distance, rounded to the nearest pixel but using the alpha
// values for sub-pixel accuracy, between the end of this scanline and the
// beginning of the given scanline.
int Font::CharScanline::DistanceTo(const CharScanline &line) const
{
	// If one glyph has no pixel in this row, return a distance of 0.
	if(!lastAlpha | !line.firstAlpha)
		return 0;
	
	// Offset the given line from this one by this character's width,
	// then calculate the distance from this character's last pixel to
	// the next character's first pixel.
	int distance = (width + line.firstPixel) - lastPixel;
	
	// Add 1 if this character's last pixel and the next character's
	// first pixel have alphas that add up to less than opaque.
	distance += (lastAlpha + line.firstAlpha < 300);
	
	return distance;
}



// DrawText constructor.
Font::DrawText::DrawText(const Surface &source, const Surface &destination, int y)
	: source(source), destination(destination), y(y), height(source.Height())
{
}



// DrawText operator().
void Font::DrawText::operator()(int sourceX, int width, int destinationX)
{
	source.Blit(sourceX, 0, width, height, destination, destinationX, y);
}



/* Copyright (c) 2008 Michael Zahniser

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. */
